if {![info exists tc_idx]} {
    set tc_idx 0
}

if {![info exists cov_en]} {
    set cov_en 0
}

# $fpath = $fdir/$fname
proc fpath2fname {args} {
    if {[regexp {^.*/([^ /]+)\s*$} $args nc fname]} {
        return $fname
    } elseif {[regexp {^\s*([^ /]+)\s*$} $args nc fname]} {
        return $fname
    } else {
        return ""
    }
}

proc fpath2fdir {args} {
    if {[regexp {^\s*([^ ]+)/[^ /]+\s*$} $args nc fdir]} {
        return $fdir
    } elseif {[regexp {^\s*[^ /]+\s*$} $args]} {
        return "."
    } else {
        return ""
    }
}

proc unifpath {refdir line} {
    if {[file exists $refdir/$line]} {
        set fpath $refdir/$line 
    } elseif {[file exists $line]} {
        set fpath $line 
    } else {
        set fpath ""
    }

    if {$fpath!=""} {
        regsub {/./} $fpath "/" fpath
        regsub {\\}  $fpath "/" fpath
    }

    return $fpath
}

proc analyline {refdir line} {
    # delete comments space
    regsub {^\s+}       $line "" line
    regsub {\/\*.*\*\/} $line "" line
    regsub {\/\/.*$}    $line "" line
    regsub {\s+$}       $line "" line

    if {[regexp {^\s*$} $line]} {
        return ""
    }
    set sfile [unifpath $refdir $line]
    if {$sfile!=""} {
        return $sfile
    } elseif {[regexp {^\s*-f\s+([^ ]+)\s*$} $line nc nfpath]} {
        set nfpath [unifpath $refdir $nfpath]
        if {[file exists $nfpath]} {
            set fr [open $nfpath r]
            set rs {}
            while {![eof $fr]} {
                gets $fr nline
                set rs [concat $rs [analyline [fpath2fdir $nfpath] $nline]]
            }
            close $fr
            return $rs 
        } else {
            echo "Error:file list '$line' not exist!"
        }
    } elseif {[regexp {^\s*\+incdir\+(.+)\s*$} $line nc sfile]} {
        set sfile [unifpath $refdir $sfile]
        if {$sfile!=""} {
            return "+incdir+$sfile"
        } else {
            echo "Error:directory '$line' not exist!"
        }
    } elseif {[regexp {^\s*-v\s+([^ ]+)\s*$} $line nc sfile]} {
        set sfile [unifpath $refdir $sfile]
        if {$sfile!=""} {
            return "-v $sfile"
        } else {
            echo "Error:file '$line' not exist!"
        }
    } elseif {[regexp {^\s*-y\s+([^ ]+)\s*$} $line nc sfile]} {
        set sfile [unifpath $refdir $sfile]
        if {$sfile!=""} {
            return "-y $sfile"
        } else {
            echo "Error:directory '$line' not exist!"
        }
    } else {
        echo "Error:unkonw '$line'!"
        return ""
    }
}

proc rlib {args} {
    global lib_work_dir

    if {[file exists $lib_work_dir/work]} {
        echo "delete lib work"
        file delete -force $lib_work_dir/work
    }

    if {[file exists $lib_work_dir/tc.wlf]} {
        echo "delete tc.wlf"
        file delete -force $lib_work_dir/tc.wlf
    }

    if {![file exists $lib_work_dir]} {
        echo "mkdir $lib_work_dir"
        file mkdir $lib_work_dir
    }

    eval vlib $lib_work_dir/work
    eval vmap work $lib_work_dir/work
}

proc com {args} {
    global tb_list
    global nl_list
    global tc_list
    global tc_idx
    global vlog_basic_cmd
    global cov_en
    global vlog_cov_opt

    set an [llength $args]

    if {$an==1 || $an==2} {
        set com_typ [lindex $args 0]
    } else {
        echo "wrong option, usage is 'com tb' 'com nl' or 'com tc 1'"
        return
    }

    set cmd_str $vlog_basic_cmd

    if {$an==1 && [string compare -nocase $com_typ tb]==0} {
        echo "compile all dut and tb files"

        if {$cov_en==1} {
            set cmd_str  "$cmd_str $vlog_cov_opt"
        }

        foreach f $tb_list {
            set cmd_str [concat $cmd_str [analyline "." $f]]
        }
    } elseif {$an==1 && [string compare -nocase $com_typ nl]==0} {
        echo "compile netlist and vendor library files"

        foreach f $nl_list {
            set cmd_str [concat $cmd_str [analyline "." $f]]
        }
    } elseif {[string compare -nocase $com_typ tc]==0} {
        if {$an==1} {
            echo "recompile current tc files"
        } else {
            set idx [lindex $args 1]

            if {$idx<[llength $tc_list] && $idx>=0} {
                set tc_idx $idx
                echo "chose tc $tc_idx"
            } else {
                echo "tc $idx is out of range"
                return
            }
        }

        set tc [lindex $tc_list $tc_idx]
        set tc_file [lindex $tc 0]

        lappend cmd_str $tc_file
    } else {
        echo "wrong option, usage is 'com tb' or 'com tc 1'"
        return
    }

    exec_cmd $cmd_str
}

proc ltc {} {
    global tc_list
    global tc_idx

    set idx  0
    foreach tc $tc_list {
        set tc_file [lindex $tc 0]
        echo "$idx: $tc_file"
        incr idx 1
    }
    echo "\ncurrent tc is $tc_idx."
}

proc cov {args} {
    global cov_en

    if {[llength $args]==1 && [regexp {^on$} $args]} {
        set cov_en 1
    } elseif {[llength $args]==1 && [regexp {^off$} $args]} {
        set cov_en 0
    } else {
        echo "wrong option, for example usage is 'cov on' or 'cov off'"
    }

    echo "\ncurrent cov_en is $cov_en."
}

proc rtc {args} {
    global tc_list
    global tc_idx
    global vlog_basic_cmd
    global vsim_basic_cmd
    global vlog_dbg_opt
    global vsim_dbg_opt
    global cov_en
    global vsim_cov_opt

    if {[llength $args]>=1} {
        set idx [lindex $args 0]

        if {[regexp {^\d+$} $idx]} {
            if {$idx<[llength $tc_list] && $idx>=0} {
                set tc_idx $idx
                echo "chose tc $tc_idx"
            } else {
                echo "tc $idx is out of range"
                return
            }
        } else {
            echo "wrong option, for example usage is 'rtc 1' or 'rtc'"
            return
        }
    } else {
        echo "rerun current tc $tc_idx"
    }
   
    set tc [lindex $tc_list $tc_idx]
    set tc_file [lindex $tc 0]

    if {[file exists $tc_file]} {
        exec_cmd "quit -sim"
        exec_cmd $vlog_basic_cmd $vlog_dbg_opt $tc_file

        if {$cov_en==1} {
            exec_cmd $vsim_basic_cmd $vsim_dbg_opt $vsim_cov_opt
        } else {
            exec_cmd $vsim_basic_cmd $vsim_dbg_opt
        }
    } else {
        echo "$tc_idx:$tc_file is not exist"
        return
    }
}

proc mtc {args} {
    global tc_list
    global tc_idx
    global vlog_basic_cmd
    global vsim_basic_cmd
    global vlog_rgrs_opt
    global vsim_rgrs_opt


    if {[llength args]==0} {
        echo "wrong option, for example usage is 'mtc 0-9' or 'mtc 0-2 5 4-9'"
        return
    }

    set tc_num [llength $tc_list]
    set idxs {}
    foreach arg $args {
        if {[regexp {^\d+$} $arg]} {
            if {$arg<$tc_num && $arg>=0} {
                lappend idxs $arg
            } else {
                echo "tc $arg out of range"
                return
            }
        } elseif {[regexp {^(\d+)-(\d+)$} $arg nc s e]} {
            if {$s>=0 && $e<$tc_num && $s<=$e} {
                for {set x $s} {$x<=$e} {incr x 1} {
                    lappend idxs $x
                }
            } else {
                echo "tc $s or $e is out of range"
                return
            }
        } else {
            echo "wrong option, for example usage is 'mtc 0-9' or 'mtc 0-2 5 4-9'"
            return
        }
    }

    file delete -force regress_status.txt

    exec_cmd "quit -sim"

    #rlib
    com tb

    foreach idx $idxs {
        set tc [lindex $tc_list $idx]
        set tc_file [lindex $tc 0]

        exec_cmd $vlog_basic_cmd $vlog_rgrs_opt $tc_file

        set rnd_seed [expr int(rand()*65536)]

        exec_cmd $vsim_basic_cmd $vsim_rgrs_opt "-sv_seed $rnd_seed"
        exec_cmd "quit -sim"
        exec_cmd "noview sim_glb.sv" [fpath2fname $tc_file]
        echo "============================== tc $idx done ================================="
        echo ""
    }

    if {[file exists regress_status.txt]} {
        eval cat regress_status.txt
    }
}

proc exec_cmd {args} {
    set cmd_str ""

    foreach arg $args {
        foreach line $arg {
            if {![regexp {^\s*$} $line]} {
                lappend cmd_str $line
            }
        }
    }
    echo $cmd_str
    eval $cmd_str
}

proc disp {} {
    echo "Modelsim/Questasim Extended Commands"
    echo {
    Script commands are:

    0, disp = display this information
    1, rlib = re-create libraries
           [work] : delete it at first and then creat it
    2, ltc  = list all tc
    3, com {options} = compile verilog files according to options, such as
           com tb      :compile TestBench files
           com nl      :compile Netlist files and vendor library files
           com tc x    :compile TestCase file, x(1~n) is testcase ID
           com tc      :compile current TestCase file
    4, rtc {options} = run tc with debug mode
           rtc x       :compile TC x(1~n), and then run it
           rtc         :compile current TC, and then run it
    5, mtc {options} = run tc one by one whith regress mode
           mtc x-y     :run tc from x to y
           mtc x       :run tc x
           mtc x y-z   :run tc x and tc from y to z
    6, cov {options} = set coverage on/off
           cov on      :set cov_en on
           cov off     :set cov_en off
    
    note: Normally, when you are debugging your testcase, use 'rlib' command 
          to delete old [work] and creat new one, and then use 'com' command 
          to compile the specified files, and then use 'rtc x' command to vsim 
          the xth testcase, x is from 1 to n.
          
          Eventually, when all testcases are ok, you can use 'mtc x-y' command 
          to vsim all the testcase one by one. Each testcase starts with run -all,
          you must finish the vsim with $finish statement in your bench code.
          
          If you want to run vsim with coverage, you can use 'cov on' command
          to enable covergae. Default value for coverage is disabled.
    }
}

disp;
